
const __CONFIG__ = require("../config.json");
import * as Fs from "fs";
import JSZip from "jszip";
import md5 from "md5";
import * as Path from "path";
import { DirTree } from "../utils/dirTree";
import FileUtils from "../utils/fileUtils";
import compareManifest from "./compareManifest";
import { IDiff, IManifest, IVersion } from "./interface";

/** 版本管理器 */
export default class VersionManager {

    /** 将字符串版本号转为 GVersion : appver.res1.res2.res3*/
    public static parserVersion(version: string): IVersion {
        let splites = version.split('.');
        return {
            ver: version,
            appBig: parseInt(splites[0]),
            appMid: parseInt(splites[1]),
            appSmall: parseInt(splites[2]),
            res: parseInt(splites[3]),
            mod: splites[4] || 'D'
        };
    }

    /** 根据目标版本信息，获取需要比对的版本信息队 */
    public static genAllVersions(srcVersion: string): IVersion[] {
        let allManifestFiles = FileUtils.resoveDirFiles(__CONFIG__.assetsPath, ".manifest", false);
        let versions: IVersion[] = [];
        let targetVersion = this.parserVersion(srcVersion);
        for (let i = 0; i < allManifestFiles.length; i++) {
            if (allManifestFiles[i].indexOf("project") != -1) {
                let versionStr = allManifestFiles[i];
                versionStr = versionStr.substring(versionStr.lastIndexOf('/') + 1);
                versionStr = versionStr.substring(versionStr.indexOf('.') + 1, versionStr.lastIndexOf("."));
                versions.push(this.parserVersion(versionStr));
            }
        }

        // cdn需要预热的文件
        let cdnFiles: string[] = [];
        // 差量资源包信息
        let targetVersionPath = Path.join(__CONFIG__.diffPackPath, `${srcVersion}.php`);
        let diffPacks = "";

        let completed = () => {
            // 写入当前版本的可差量更新包
            Fs.writeFileSync(targetVersionPath, `<?php\n$diffversions = [\n${diffPacks}]; \n?>`, "utf-8");
            // 拷贝当前版本的version文件到待发布目录
            this.copyOneVerManifestToDiffPack(srcVersion);
            cdnFiles.push(`project.${targetVersion.ver}.manifest`);
            cdnFiles.push(`version.${targetVersion.ver}.manifest`);
            this.genCdnDiffJson(targetVersion.ver, cdnFiles);
            this.genCdnUrlsCsv(targetVersion.ver, cdnFiles);
            console.log("completed gen all version diff!");
        }

        if (versions.length > 1) {
            versions.sort((va: IVersion, vb: IVersion) => {
                return va.res - vb.res;
            })

            let versionDiffs = Object.create(null);
            let waterfallGenDiff = (index: number) => {
                if (index < versions.length) {
                    if (versions[index].ver == targetVersion.ver) {
                        waterfallGenDiff(index + 1);
                        return;
                    }
                    if (this.compareVersion(versions[index], targetVersion)) {
                        let diff = this.genVersionDiff(versions[index], targetVersion);
                        this.packDiffFile(diff, () => {
                            let zipFileName = `${diff.newVersion}-${diff.oldVersion}.zip`
                            let zipFilePath = Path.join(__CONFIG__.diffPackPath, zipFileName);
                            const fileBuff = Fs.readFileSync(zipFilePath);
                            const size = fileBuff.length;
                            const md5Code = md5(fileBuff);
                            const url = `${diff.newVersion}-${diff.oldVersion}.${md5Code.slice(0, 8)}.zip`;
                            Fs.renameSync(zipFilePath, Path.join(__CONFIG__.diffPackPath, url));
                            versionDiffs[diff.oldVersion] = { url, size, md5: md5Code };
                            waterfallGenDiff(index + 1);
                            return;
                        });
                    } else {
                        waterfallGenDiff(index + 1);
                        return;
                    }
                } else {
                    let keys = Object.keys(versionDiffs);
                    let targetDiff: string | null = null;
                    for (let i = 0; i < keys.length; i++) {
                        let versionDiff = versionDiffs[keys[i]];
                        let phpVersionDiff = `["url" => "${versionDiff.url}", "size" => ${versionDiff.size}, "md5" => "${versionDiff.md5}"]`;
                        diffPacks += `\t"${keys[i]}" => ${phpVersionDiff},\n`;
                        cdnFiles.push(versionDiff.url);
                        if (keys[i] == versions[0].ver) {
                            targetDiff = phpVersionDiff;
                        }
                    }
                    diffPacks += `\t"${targetVersion.ver}" => ${targetDiff},\n`;
                    completed();
                }
            }
            waterfallGenDiff(0);
        } else {
            diffPacks = `\t"${targetVersion.ver}" => null,\n`;
            completed();
        }
        return versions;
    }

    /** 生成指定版本的差异化文件 */
    public static genDiffVersion(sOldVersion: string, sNewVersion: string) {
        let iOldVersion = this.parserVersion(sOldVersion);
        let iNewVersion = this.parserVersion(sNewVersion);
        let diff = this.genVersionDiff(iOldVersion, iNewVersion);
        this.packDiffFile(diff, console.log);
    }

    /** 判断是否再同一个热更区域 */
    public static compareVersion(oldVersion: IVersion, newVersion: IVersion) {
        if (oldVersion.appBig != newVersion.appBig || oldVersion.appMid != newVersion.appMid) {
            return false;
        }
        if (oldVersion.res > newVersion.res) {
            return false;
        }
        return true;
    }

    /** 生成俩个版本之间的差异文件 */
    public static genVersionDiff(versionA: IVersion, versionB: IVersion): IDiff {
        let fileManifestA = Path.join(__CONFIG__.assetsPath, `/project.${versionA.ver}.manifest`);
        let fileManifestB = Path.join(__CONFIG__.assetsPath, `/project.${versionB.ver}.manifest`);
        let manifestA: IManifest = JSON.parse(Fs.readFileSync(fileManifestA, "utf-8"));
        let manifestB: IManifest = JSON.parse(Fs.readFileSync(fileManifestB, "utf-8"));
        let diff = compareManifest(manifestA, manifestB);
        return diff;
    }

    /** 根据版本差异打包差异文件 */
    public static packDiffFile(diff: IDiff, callBack?: Function) {
        let dir = FileUtils.convertListToTree(diff.diffFiles);
        let dirZips = new Map<string, JSZip>();
        let resolveDirZiper = (dir: DirTree): JSZip => {
            let ziper = dirZips.get(dir.deep);
            if (ziper) return ziper;
            if (!dir.parent) {
                ziper = new JSZip();
                dirZips.set(dir.deep, ziper);
            } else {
                let parentZiper = dirZips.get(dir.parent.deep);
                if (!parentZiper) {
                    parentZiper = resolveDirZiper(<DirTree>dir.parent);
                }
                ziper = <JSZip>parentZiper.folder(dir.name);
                dirZips.set(dir.deep, ziper);
            }
            return ziper;
        }
        dir.forEachFile((file, dir) => {
            let ziper = resolveDirZiper(dir);
            let fullFile = dir.deep + '/' + file;
            fullFile = fullFile.substring(fullFile.indexOf("/") + 1);
            fullFile = Path.join(__CONFIG__.assetsPath, fullFile);
            ziper!.file(file, Fs.readFileSync(fullFile));
        });
        let rootZiper = dirZips.get(dir.deep);
        FileUtils.mkdirSync(__CONFIG__.diffPackPath);
        let zipFilePath = Path.join(__CONFIG__.diffPackPath, `${diff.newVersion}-${diff.oldVersion}.zip`);

        rootZiper!.generateNodeStream({
            type: "nodebuffer",
            streamFiles: true,
        })
            .pipe(Fs.createWriteStream(zipFilePath))
            .on('finish', () => {
                console.log(`gen diff zip sucess: ${zipFilePath}`);
                callBack!();
            })
            .on("error", (e: Error) => {
                console.log(`fen diff zip failed: ${zipFilePath}`);
            })
    }

    /** 同步所有assets目录下的资源到diff目录 */
    public static copyAllManifestToDiffPack() {
        let allManifestFiles = FileUtils.resoveDirFiles(__CONFIG__.assetsPath, ".manifest", false);
        for (let i = 0; i < allManifestFiles.length; i++) {
            if (allManifestFiles[i].indexOf("project") != -1) {
                let versionStr = allManifestFiles[i];
                versionStr = versionStr.substring(versionStr.indexOf('.') + 1, versionStr.lastIndexOf("."));
                this.copyOneVerManifestToDiffPack(versionStr);
            }
        }
    }

    /** 拷贝最新的版本文件到目标目录 */
    public static copyOneVerManifestToDiffPack(version: string) {
        let projectPath = Path.join(__CONFIG__.assetsPath, `/project.${version}.manifest`);
        let diffProjectPath = Path.join(__CONFIG__.diffPackPath, `/project.${version}.manifest`);
        let versionPath = Path.join(__CONFIG__.assetsPath, `/version.${version}.manifest`);
        let diffVersionPath = Path.join(__CONFIG__.diffPackPath, `/version.${version}.manifest`);
        FileUtils.copyFile(projectPath, diffProjectPath);
        FileUtils.copyFile(versionPath, diffVersionPath);
    }

    /** 生成cdn预热文件 */
    public static genCdnDiffJson(version: string, versionCdnFiles: string[]) {
        let filePath = Path.join(__CONFIG__.diffPackPath, `diff.json`);
        Fs.writeFileSync(filePath, JSON.stringify(versionCdnFiles, null, 4));
        console.log("gen cdn file list sucess!");
    }

    /** 生成 urls.csv */
    public static genCdnUrlsCsv(version: string, versionCdnFiles: string[]) {
        let filePath = Path.join(__CONFIG__.diffPackPath, `urls.csv`);
        let reuslt: string[] = [];
        for (let i: number = 0; i < versionCdnFiles.length; i++) {
            reuslt.push(__CONFIG__.csvPath + versionCdnFiles[i]);
        }
        Fs.writeFileSync(filePath, reuslt.join('\n'));
        console.log("gen cdn url.csv file sucess!");
    }

    /** 解压指定资源包 */
    public static unzipVersion(version: string) {
        let zipPath = Path.join(__CONFIG__.assetsPath, `ver.${version}.zip`);
        if (!Fs.existsSync(zipPath)) {
            throw new Error("file now exist. " + zipPath);
        }
        let jsZiper = new JSZip();
        jsZiper.loadAsync(Fs.readFileSync(zipPath)).then(zipData => {
            let fileList = Object.keys(zipData.files);
            let index = 0;
            fileList.forEach(path => {
                let fullPath = Path.join(__CONFIG__.assetsPath, path);
                let file = zipData.files[path];
                if (file.dir) {
                    if (!Fs.existsSync(fullPath)) {
                        Fs.mkdirSync(fullPath);
                    }
                    index++;
                    console.log("unzip: " + fullPath);
                    return;
                } else {
                    file.async("uint8array").then(content => {
                        Fs.writeFileSync(fullPath, content);
                        index++
                        console.log("unzip: " + fullPath);
                        if (index >= fileList.length) {
                            console.log("unzip completed! version = " + version);
                        }
                    })
                }
            })
        })
    }

    /** 发布正式版本 */
    public static publish(version: string) {
        // 检查是否生成差量版本信息文件：
        let targetVersionPath = Path.join(__CONFIG__.diffPackPath, `${version}.php`);
        if (!Fs.existsSync(targetVersionPath)) {
            throw new Error(`version diff packages info php not exist! path = ${targetVersionPath}, please run [-a][version] to gennator!`);
        }
        // 拷贝当前版本的version文件到待发布目录
        this.copyOneVerManifestToDiffPack(version);
        let versionPath = Path.join(__CONFIG__.assetsPath, `${version}_sh/version.php`);
        let distPath = Path.join(__CONFIG__.diffPackPath, `version.php`);
        if (!Fs.existsSync(versionPath)) {
            throw new Error(`version.php not exist, path = ${versionPath}, please unzip ver.${version}.zip.`);
        }
        FileUtils.copyFile(versionPath, distPath);
        console.log(`completed publish!`);
    }

    public static publishWhite(version: string) {
        // 检查是否生成差量版本信息文件：
        let targetVersionPath = Path.join(__CONFIG__.diffPackPath, `${version}.php`);
        if (!Fs.existsSync(targetVersionPath)) {
            throw new Error(`version diff packages info php not exist! path = ${targetVersionPath}, please run [-a][version] to gennator!`);
        }
        // 拷贝当前版本的version文件到待发布目录
        this.copyOneVerManifestToDiffPack(version);
        let versionPath = Path.join(__CONFIG__.assetsPath, `${version}_sh/version-private.php`);
        let distPath = Path.join(__CONFIG__.diffPackPath, `version-private.php`);
        if (!Fs.existsSync(versionPath)) {
            throw new Error(`version.php not exist, path = ${versionPath}, please unzip ver.${version}.zip.`);
        }
        FileUtils.copyFile(versionPath, distPath);
        console.log(`completed publish!`);
    }
}